/**********************************************************************
 * @file mmu.c
 * @remark 
 **********************************************************************/
#include "mmu.h"
#include "cp15.h"
#include "sys_dbg.h"
#include "uart.h"

//无效的条目类型
#define MMU_ENTRY_FAULT 0
//一级粗页条目
#define MMU_ENTRY_COARSE 1
//一级Section条目
#define MMU_ENTRY_SECTION 2
//一级细页条目
#define MMU_ENTRY_FINE 3

//二级大页条目(64KB)
#define MMU_ENTRY_LARGE 1
//二级小页条目(4KB)
#define MMU_ENTRY_SMALL 2
//二级超小页条目(1KB)
#define MMU_ENTRY_TINY  3

#define MMU_ENTRY_CB_BIT 2

#define MMU_ENTRY_L1_BIT4 4

#define MMU_ENTRY_L1_DOMAIN_BIT 5

#define MMU_ENTRY_SECTION_AP_BIT 10

#define MMU_ENTRY_COARSE_BIT 10

#define MMU_ENTRY_SECTION_BIT 20
#define MMU_L1_ADDR_SHIFT MMU_ENTRY_SECTION_BIT
#define MMU_L1_ADDR_MASK (_BIT(MMU_L1_ADDR_SHIFT)-1)

#define MMU_ENTRY_FINE_BIT 12

#define MMU_ENTRY_L2_AP 4
#define MMU_ENTRY_L2_AP0 4
#define MMU_ENTRY_L2_AP1 6
#define MMU_ENTRY_L2_AP2 8
#define MMU_ENTRY_L2_AP3 10

#define CB_ATTR_CB		(3)  //cache_on, write_back
#define CB_ATTR_CNB		(2)  //cache_on, write_through 
#define CB_ATTR_NCB     (1)  //cache_off,WR_BUF on
#define CB_ATTR_NCNB	(0)  //cache_off,WR_BUF off

enum
{
    MMU_ADDR_ALIGN_INVALID=-1,
    MMU_ADDR_ALIGN_1KB=0,
    MMU_ADDR_ALIGN_4KB,
    MMU_ADDR_ALIGN_64KB,
    MMU_ADDR_ALIGN_1MB,
    MMU_ADDR_ALIGN_COUNT,
};


// enum{
//     ACCESS_ATTR_AP00_S0_R0=0, //NO ACCESS
//     ACCESS_ATTR_AP00_S1_R0,   //SVC---Read Only, USER---NO ACCESS
//     ACCESS_ATTR_AP00_S0_R1,   //SVC---Read Only, USER---Read Only
//     ACCESS_ATTR_AP00_S1_R1,   //RESERVE
//     ACCESS_ATTR_AP01_Sx_Rx,   //SVC---RW, USER---NO-ACCESS,
//     ACCESS_ATTR_AP10_Sx_Rx,   //SVC---RW, USER---Read only,
//     ACCESS_ATTR_AP11_Sx_Rx,   //SVC---RW, USER---RW,
//     ACCESS_ATTR_APxx_S1_R1,   //RESERVE
//     ACCESS_ATTR_COUNT,
// };

enum{
    AP_ATTR_NOACCESS=0,             //NO ACCESS
    AP_ATTR_SVC_RW_USER_NOACCESS,   //SVC---RW, USER---NO-ACCESS,
    AP_ATTR_SVC_RW_USER_RO,         //SVC---RW, USER---Read only,
    AP_ATTR_ALL_RW,                 //SVC---RW, USER---RW, 
    AP_ATTR_COUNT,
};


/************************************************************
 * 对于范围大于1MB的自动按照Section进行映射
 * 范围大于64KB按照large进行映射
 * 范围大于4Kb按照smallPage进行映射
 * 范围大于1kb按照tinyPage进行映射
 * 粗页对应的二级页表只能是LargePage(KB) 或者SmallPage(4KB)
 * Fine Page对应的二级页表可以是LargePage(KB) 或者SmallPage(4KB)或者TinyPage(1KB)
 * 
 * 
 * 禁止或者使能MMU之前应当注意的事项：
 *     1. 在使能MMU之前,要在内存中建立页表，同时CP15中的各相关寄存器必须完成初始化
 *     2. 非平板存储方式时，则在使能或者禁止MMU时，应在此之前清除cache中的当前地址变换条目
 * 
 ************************************************************/

typedef struct MemoryRegionMap{
    UINT32 phyStartAddr;        //物理起始地址
    UINT32 phyLimitAddr;        //物理结束地址
    UINT32 virtualStartAddr;    //虚拟起始地址
    UINT32 domainType;    //域的类型
    UINT32 isValid:1;//映射是否有效
    UINT32 domain:4;//所属的域
    UINT32 apAttr:2;
    UINT32 cbAttr:2;
    UINT32 padding:23;
}MemoryRegion;

extern UINT32 Image$$VECTOR$$ZI$$Limit;
extern UINT32 Image$$MMUL1Base$$ZI$$Base;
extern UINT32 Image$$MMUL1Base$$ZI$$Limit;
extern UINT32 Image$$MMUL2Base$$ZI$$Base;
extern UINT32 Image$$MMUL2Base$$ZI$$Limit;
extern char __START[];
MemoryRegion g_MemoryMap[]={
    {
        .phyStartAddr=0x40000000,
        .phyLimitAddr = 0x40001000,
        .virtualStartAddr =0x40000000,
        .domainType = CR3_DOMAIN_ATTR_CLIENT,
        .domain = 0,
        .apAttr = AP_ATTR_ALL_RW,
        .cbAttr = CB_ATTR_CNB,
    },//
    {
        .phyStartAddr = 0x30000000,
        .phyLimitAddr = (UINT32)(&Image$$VECTOR$$ZI$$Limit),
        .virtualStartAddr =0x00000000,
        .domainType = CR3_DOMAIN_ATTR_CLIENT,
        .domain = 0,
        .apAttr = AP_ATTR_ALL_RW,
        .cbAttr = CB_ATTR_CNB,
    },//SDRAM-1
    {
        .phyStartAddr = (UINT32)(&Image$$MMUL1Base$$ZI$$Base),
        .phyLimitAddr = (UINT32)(&Image$$MMUL2Base$$ZI$$Limit),
        .virtualStartAddr =(UINT32)(&Image$$MMUL1Base$$ZI$$Base),
        .domainType = CR3_DOMAIN_ATTR_CLIENT,
        .domain = 0,
        .apAttr = AP_ATTR_ALL_RW,
        .cbAttr = CB_ATTR_CNB,
    },//SDRAM-TTB
    {
        .phyStartAddr = 0x48000000,
        .phyLimitAddr = 0x60000000,
        .virtualStartAddr =0x48000000,
        .domainType = CR3_DOMAIN_ATTR_CLIENT,
        .domain = 0,
        .apAttr = AP_ATTR_ALL_RW,
        .cbAttr = CB_ATTR_NCNB,
    },//GPIO
    {
        .phyStartAddr = 0x00000000,
        .phyLimitAddr = 0x08000000,
        .virtualStartAddr =0xA0000000,
        .domainType = CR3_DOMAIN_ATTR_CLIENT,
        .domain = 0,
        .apAttr = AP_ATTR_ALL_RW,
        .cbAttr = CB_ATTR_CNB,
    },//NORFLASH
};

UINT32 *g_MMU_L1BasePtr=NULL;
UINT32 *g_MMU_L2BasePtr=NULL;
UINT32 *g_MMU_L2LimitPtr=NULL;


// static INT32 mmu_getAddrAlignType(UINT32 addr){
//     if(addr & (SZ_1MB-1)==0)
//         return MMU_ADDR_ALIGN_1MB;
    
//     if(addr & (SZ_64KB-1)==0)
//         return MMU_ADDR_ALIGN_64KB;

//     if(addr & (SZ_4KB-1)==0)
//         return MMU_ADDR_ALIGN_4KB;
    
//     if(addr & (SZ_1KB-1)==0)
//         return MMU_ADDR_ALIGN_1KB;
    
//     return MMU_ADDR_ALIGN_INVALID;
// }

static void mmu_setCR3Domain(void){
    //设置所属域的属性
    UINT32 domain=0;
    INT32 i=0;

    for(i=0;i<CR3_DOMAIN_COUNT;i++){
        if(i==CR3_DOMAIN_CLIENT_INDEX){
            domain |= (CR3_DOMAIN_ATTR_CLIENT<<i);
        }else if(i==CR3_DOMAIN_MANAGER_INDEX){
            domain |= (CR3_DOMAIN_ATTR_MANAGER<<i);
        }else{
            domain |= (CR3_DOMAIN_ATTR_NOACCESS<<i);
        }
    }
     CP15_SetDomain(domain); 
}


static void mmu_config(UINT32 *pL1BaseAddr,UINT32 *pL2BaseAddr,UINT32 *pL2LimitAddr)
{
    SYS_ASSERT(pL1BaseAddr!=NULL && pL2BaseAddr!=NULL && pL2LimitAddr!=NULL);
    // pL1BaseAddr应当是16KB对齐
    SYS_ASSERT( ((UINT32)pL1BaseAddr)&(SZ_16KB-1) !=0);
    // 二级页表地址
    SYS_ASSERT(pL2LimitAddr>pL2BaseAddr);

    g_MMU_L1BasePtr=pL1BaseAddr;
    g_MMU_L2BasePtr=pL2BaseAddr;
    g_MMU_L2LimitPtr=pL2LimitAddr;
    
    //未设置映射的区域采用平板映射，即物理地址=虚拟地址
    for(INT32 i=0;i<4096 ;i++){
        g_MMU_L1BasePtr[i]= ((i<<MMU_L1_ADDR_SHIFT)
                            |(AP_ATTR_ALL_RW<<MMU_ENTRY_SECTION_AP_BIT)
                            |(0<<MMU_ENTRY_L1_DOMAIN_BIT)
                            |(1<<MMU_ENTRY_L1_BIT4)
                            |(CB_ATTR_CNB<<MMU_ENTRY_CB_BIT)
                            |MMU_ENTRY_SECTION);
    }
    //调整内存映射
    UINT32 rgnBaseAddr,rgnLimitAddr,vaAddr;
    INT32 index;
    for(INT32 i=0;i<ARRAY_SIZE(g_MemoryMap);i++)
    {
        rgnBaseAddr = g_MemoryMap[i].phyStartAddr;
        rgnBaseAddr = rgnBaseAddr & ~(SZ_1MB-1);

        rgnLimitAddr =  g_MemoryMap[i].phyLimitAddr;
        if((rgnLimitAddr-rgnBaseAddr)<SZ_1MB)
            rgnLimitAddr = rgnBaseAddr + SZ_1MB;
        
        vaAddr = g_MemoryMap[i].virtualStartAddr;
        vaAddr = vaAddr & ~(SZ_1MB-1);
        
        while(rgnBaseAddr<rgnLimitAddr){
            index = vaAddr>>MMU_L1_ADDR_SHIFT;
            *(pL1BaseAddr+index) = ( rgnBaseAddr | ((g_MemoryMap[i].apAttr)<<MMU_ENTRY_SECTION_AP_BIT)
                                    | ((g_MemoryMap[i].domain)<<MMU_ENTRY_L1_DOMAIN_BIT)
                                    | (1<<MMU_ENTRY_L1_BIT4)
                                    | ((g_MemoryMap[i].cbAttr)<<MMU_ENTRY_CB_BIT)
                                    | MMU_ENTRY_SECTION );
            rgnBaseAddr += SZ_1MB;
            vaAddr +=SZ_1MB;
        }
    }
}

void mmu_init(void){
    //禁止MMU
    CP15_DisableICache();
    CP15_DisableDCache();
    //清空DCache，使得Dcache中的内容写到主存中去
     INT32 i,j;
    for(i=0;i<64;i++){
        for(j=0;j<8;j++){
            CP15_CleanSingleDCacheEntryIdx((i<<26)|(j<<5));
        }	
    }
    //使得ICache中的内容无效化
	CP15_InvalidateICache();

    CP15_DisableMMU();
    
    CP15_InvalidateTLB();

    mmu_config(&Image$$MMUL1Base$$ZI$$Base,&Image$$MMUL2Base$$ZI$$Base,&Image$$MMUL2Base$$ZI$$Limit);
    mmu_setCR3Domain();

    UINT32 ttbBaseAddr=(UINT32)(&Image$$MMUL1Base$$ZI$$Base);
    ttbBaseAddr &= ~ (1<<14-1);

    CP15_SetTTBBase(ttbBaseAddr);
    
    CP15_SetProcessId(0x0);
    
    CP15_EnableAlignCheck();

    CP15_EnableMMU();
    
    CP15_EnableICache();
    
    CP15_EnableDCache();
}
